#pragma once

#include <cassert>
#include <vector>


typedef int CellId;

template <class CellT>
class CAGrid2D
{
public:
  typedef const CellT & ConstCellRef;

  int getWidth() const { return m_width; }
  int getHeight() const { return m_height; }
  int getBuffersCount() const { return m_bufCount; }

  int getReadBuffer() const { return m_readBufId; };
  void setReadBuffer(int id)
  {
    m_readBufId = id;
    m_readBuf = &(m_data[m_readBufId]);
  }

  int getWriteBuffer() const { return m_writeBufId; };
  void setWriteBuffer(int id)
  {
    m_writeBufId = id;
    m_writeBuf = &(m_data[m_writeBufId]);
  }

  void cycleBuffers()
  {
    m_readBufId = (m_readBufId + 1) % m_bufCount;
    m_writeBufId = (m_writeBufId + 1) % m_bufCount;
    m_readBuf = &(m_data[m_readBufId]);
    m_writeBuf = &(m_data[m_writeBufId]);
  }

  void setTorusBound() { m_bound = TORUS; }
  void setConstBound(ConstCellRef cell)
  {
    m_bound = CONSTANT;
    m_boundCell = cell;
  }

  ConstCellRef getCellById(CellId id) const { return (*m_readBuf)[id]; }
  void setCellById(CellId id, ConstCellRef cell) { (*m_writeBuf)[id] = cell; }

  ConstCellRef getCell(int x, int y) const 
  { 
    CellId id = pos2CellId(x, y);
    if (id < 0)
      return m_boundCell;
    else
      return (*m_readBuf)[id]; 
  }
  void setCell(int x, int y, ConstCellRef cell) 
  { 
    CellId id = pos2CellId(x, y);
    if (id >=0)
      (*m_writeBuf)[id] = cell; 
  }

  CellId pos2CellId(int x, int y) const
  {
    if (x < 0 || x >= m_width || y < 0 || y >= m_height)
    {
      if (m_bound == CONSTANT)
        return -1;

      // todo: better wrap around
      x = (x + m_width) % m_width;
      y = (y + m_height) % m_height;
    }
    return y * m_width + x;
  }

protected:
  CAGrid2D(int width, int height, int bufCount = 1)
    : m_data(bufCount), m_bufCount(bufCount), m_width(width), m_height(height), 
    m_cellCount(width * height)
  {
    assert(bufCount >= 1 && width > 0 && height > 0);

    for (int i = 0; i != bufCount; ++i)
      m_data[i].resize(m_cellCount);   

    m_readBufId = 0;
    m_readBuf = &(m_data[m_readBufId]);
    m_writeBufId = 0;
    m_writeBuf = &(m_data[m_writeBufId]);

    setTorusBound();
  }
  
  CellT & getMutableCell(CellId id) { return (*m_writeBuf)[id]; }

private:
  enum BoundType { TORUS, CONSTANT };

  typedef std::vector<CellT> Buffer;

  std::vector<Buffer> m_data;
  Buffer * m_readBuf;
  Buffer * m_writeBuf;
  int m_readBufId;
  int m_writeBufId;

  int m_bufCount;
  int m_width;
  int m_height;
  int m_cellCount;

  BoundType m_bound;
  CellT m_boundCell;
};
